/*
 * Copyright (C) 2018 Tom Li <tomli at tomli.me>.
 *
 * This software is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3, or (at your option) any
 * later version.
 *
 * This software is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See COPYING for terms and conditions.
 *
 * GPL or LGPL does __not__ require attribution beyond preserving and
 * following the license, but if you find my code is useful or inspirational
 * to your own project, I'd be thankful if you attribute my name.
 */

#include <stdint.h>
#include <stdbool.h>
#include <string.h>

#ifndef __SYSCALLS_H
#include "syscalls.h"
#define __SYSCALLS_H
#endif

uint16_t bank1_get(void)
{
__asm
    in a, (0x06)
    ld l, a
    in a, (0x0E)
    ld h, a
__endasm;
}

void bank1_swap(const uint16_t page) __z88dk_fastcall __naked
{
    USED_IN_ASM(page);

__asm
    ld a, l
    out (0x06), a
    ld a, h
    out (0x0E), a
    ret
__endasm;
}

void bank1_fastswap(const uint8_t page) __z88dk_fastcall __naked
{
    USED_IN_ASM(page);

__asm
    ld a, l
    out (0x06), a
    ret
__endasm;
}

uint8_t cpuclk_get(void)
{
__asm
    in a, (0x20)
    ld l, a
__endasm;
}

void cpuclk_set(const uint8_t speed)
{
    USED_IN_ASM(speed);

__asm
    ld a, 4 (ix)
    out (0x20), a
__endasm;
}

void clear_screen(void)
{
__asm
    rst 0x28
    .dw 0x453D
__endasm;
}

void getkey(void)
{
__asm
    rst 0x28
    .dw 0x495D
__endasm;
}

int putchar(int c)
{
    USED_IN_ASM(c);

__asm
    ld a, 4 (ix)
    cp a, #10
    jr nz, putchr

    rst 0x28
    .dw 0x4525
    jp putchar_retn

putchr:
    rst 0x28
    .dw 0x44FB

putchar_retn:
__endasm;

    return c;
}

void disphl(uint8_t row, uint8_t col, const uint16_t i)
{

    cursor_row = row;
    cursor_col = col;

    USED_IN_ASM(i);

__asm
    ld l, 6 (ix)
    ld h, 7 (ix)

    rst 0x28
    .dw 0x44FE
__endasm;
}

void fillrect(uint16_t x1, uint16_t x2, uint8_t y1, uint8_t y2)
{
    USED_IN_ASM(x1);
    USED_IN_ASM(x2);
    USED_IN_ASM(y1);
    USED_IN_ASM(y2);

__asm
    ld l, 4 (ix)
    ld h, 5 (ix)
    
    ld e, 6 (ix)
    ld d, 7 (ix)

    ld b, 8 (ix)
    ld c, 9 (ix)

    rst 0x28
    .dw 0x4D4A
__endasm;
}

const bool findsym(const uint8_t type, const unsigned char *name, uint16_t *page, uint16_t *addr)
{
    unsigned char op[9];

    if (strlen(name) > 7) {
        goto not_found;
    }

    op[0] = type;
    strncpy(&op[1], name, 8);
    op[8] = '\0';

    USED_IN_ASM(page);
    USED_IN_ASM(addr);

__asm
    ld hl, #0
    add hl, sp

    rst 0x28
    .dw 0x4177

    rst 0x28
    .dw 0x42E8

    jr c, 2$  /* not found */
    jp 1$     /* found */

1$: /* found */

    /* page */
    ld a, b
    ld c, 7 (ix)
    ld b, 8 (ix)
    ld (bc), a
    inc bc
    ld a, #0
    ld (bc), a

    /* addr */
    ld  c, 9 (ix)
    ld  b, 10 (ix)
    ld a, e
    ld  (bc), a
    inc bc
    ld a, d
    ld  (bc), a

__endasm;

    *addr -= 0x4000;  /* remove base addr */

    /* See "Variable Storage in the User Archive" @ WikiTI */
    *addr += 9;  /* metadata */
    *addr += 8;  /* variable name */
    *addr += 2;  /* size word */

__asm
    ld l, #0
    jp 3$  /* return */
__endasm;

not_found:

__asm
2$: /* not_found */

    ld l, #1

3$: /* return */

__endasm;

}

const bool findapp(const unsigned char *name, uint16_t *page, uint16_t *addr)
{
    uint16_t current_page = 0xFF63;

    uint16_t orig_page = bank1_get();
    uint8_t len = strlen(name);

    while (current_page != 0x00) {
        bank1_swap(current_page);

        if (bank1[0] != 0x80) {
            current_page--;
            goto next;
        }

        uint8_t *app_pages = NULL;
        uint8_t *app_name = NULL;
        uint8_t app_begin = 0;

        for (uint8_t idx = 1; idx < 40; idx++) {
            if (bank1[idx] != 0x80) {
                continue;
            }
            if (bank1[idx + 1] == 0x81) {
                app_pages = &bank1[idx + 2];
            }

            if (bank1[idx + 1] == 0x40 + len) {
                app_name = &bank1[idx + 2];
            }
            if (bank1[idx + 1] == 0x70) {
                app_begin = idx + 2;
            }
        }
        if (app_pages == NULL || app_name == NULL || app_begin == 0) {
            current_page--;
            goto next;
        }

        if (memcmp(name, app_name, len) == 0) {
            *page = current_page;
            *addr = app_begin;
            bank1_swap(orig_page);
            return 0;
        }
        else {
            current_page -= *app_pages;
        }

next:
        if (current_page == 0xFF00 - 1) {
            current_page = 0x007F;
        }
    }

    /* TODO: determine the last application page, and abort early. */

    bank1_swap(orig_page);
    return 1;
}

/*
 * DCSE routines
 */

void clear_lcd(void)
{
__asm
    call 0x402A
__endasm;
}

void drawsprite_1bit(uint16_t x, uint16_t y, uint8_t *palette, uint8_t *sprite)
{
    USED_IN_ASM(x);
    USED_IN_ASM(y);
    USED_IN_ASM(sprite);

    uint16_t addr = (uint16_t) palette;
    sprite[0] = addr & 0xFFU;
    sprite[1] = (addr >> 8) & 0xFFU;

__asm
    ld e, 4 (ix)
    ld d, 5 (ix)

    ld l, 6 (ix)
    ld h, 7 (ix)

    ld c, 10 (ix)
    ld b, 11 (ix)

    push bc
    pop ix

    call 0x4036
__endasm;
}

void drawsprite_8bit(uint16_t x, uint16_t y, uint8_t *palette, uint8_t *sprite)
{
    USED_IN_ASM(x);
    USED_IN_ASM(y);
    USED_IN_ASM(sprite);

    if (palette != NULL) {
        uint16_t addr = (uint16_t) palette;
        sprite[0] = addr & 0xFFU;
        sprite[1] = (addr >> 8) & 0xFFU;
    }
    else {
        sprite[0] = 0x00;
        sprite[1] = 0x00;
    }

__asm
    ld e, 4 (ix)
    ld d, 5 (ix)

    ld l, 6 (ix)
    ld h, 7 (ix)

    ld c, 10 (ix)
    ld b, 11 (ix)

    push bc
    pop ix

    call 0x4042
__endasm;
}

/*
 * The following code is obtained from
 * https://github.com/faithanalog/wiki/blob/master/ti84cse/HalfRes.md
 *
 * Thanks for Unknown Loner's detailed tutorial and documentation!
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Unknown Loner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 */

void lcd_halfres(void)
{
__asm
    ld a, #0x01
    out (0x10), a
    out (0x10), a
    ld a, #0x04

    out (0x11), a
    xor a
    out (0x11), a

    ld a, #0x07
    out (0x10), a
    out (0x10), a
    ld a, #0x30
    out (0x11), a
    ld a, #0x33
    out (0x11), a

    ld a, #0x80
    out (0x10), a
    out (0x10), a
    xor a
    out (0x11), a
    out (0x11), a

    ld a, #0x83
    out (0x10), a
    out (0x10), a
    xor a
    out (0x11), a
    ld a, #160
    out (0x11), a

    ld c, #0x11

    ld a, #0x81
    out (0x10), a
    out (0x10), a
    xor a
    out (0x11), a
    out (0x11), a

    ld a, #0x84
    out (0x10), a
    out (0x10), a
    xor a
    out (0x11), a
    out (0x11), a

    ld de, #159

    ld a, #0x82
    out (0x10), a
    out (0x10), a
    out (c), d
    out (c), e

    ld a, #0x85
    out (0x10), a
    out (0x10), a
    out (c), d
    out (c), e

__endasm;
}

void lcd_fullres(void)
{
__asm
    ld a, #0x01
    out (0x10), a
    out (0x10), a
    xor a
    out (0x11), a
    out (0x11), a

    ld a, #0x07
    out (0x10), a
    out (0x10), a
    ld a, #0x01
    out (0x11), a
    ld a, #0x33
    out (0x11), a
__endasm;
}
